/*
 * client.c
 *
 *  Created on: 2012-01-05
 *      Author: mateusz
 */

#include <ncurses.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <signal.h>
#include <sys/sem.h>
#include <sys/shm.h>
#include <sys/msg.h>
#include <unistd.h>
#include <sys/wait.h>
#include "client.h"
#include "shared.h"
#include <time.h>

int shmSem, shmid, running = 1, killedByServer = 0;

int main() {
	int publicServerQueue, privateQueue, pid, tmp, clientQueue;
	int childs[SUBWINDOWS];

	// add signal action
	signal(SIGINT, stop);

	// connect to server public queue
	publicServerQueue = getQueue(SERVER_QUEUE, 0600, 0);

	// exit if server is not running
	if (publicServerQueue == -1) {
		printf("%s\n", msgs[LOG_SERVER_DOES_NOT_EXIST]);
		return -1;
	}

	// send init msg to server
	sendIntMsg(publicServerQueue, CS_INIT, getpid());

	// get private queues ids (usually pid)
	tmp = getIntMsg(publicServerQueue, getpid());
	privateQueue = getQueue(tmp, 0600, 0);
	printf("%s\n", msgs[LOG_WAITING_FOR_ALL_PLAYERS]);
	getIntMsg(privateQueue, CS_CLIENT_START);

	if (running == 1) {
		// set client queue
		clientQueue = getQueue(getpid(), 0600 | IPC_CREAT, 1);

		//set semaphore on shared memory acces
		shmSem = newSem(getpid(), 1);

		// create shared memory
		shmid = newShm(getpid(), sizeof(shm));

		// substract log listener process
		pid = fork();
		// stats window
		if (pid == 0) {
			logger(privateQueue, clientQueue, shmid);

		}
		// main process
		else {
			childs[0] = pid;

			// substract stats listener process
			pid = fork();
			// stats window
			if (pid == 0) {
				stats(privateQueue, clientQueue, shmid);
			}
			// main process
			else {
				childs[1] = pid;

				initscr();
				clear();
				refresh();
				noecho();
				cbreak();

				// wait for start message
				//getIntMsg(publicServerQueue, START_ID);
				pid = fork();
				// stats window
				if (pid == 0) {
					command(privateQueue, clientQueue, shmid);
				}
				// main process
				else {
					childs[2] = pid;

					updateScreen(clientQueue, shmid);

					// kill all childs
					int i;
					for (i = 0; i <= 2; i++) {
						kill(childs[i], SIGINT);
						wait(NULL);
					}

					// clean ipcs
					cleanEverything(clientQueue);

					// send last msg to server
					if (killedByServer == 0) {
						sendIntMsg(publicServerQueue, CS_EXIT, getpid());
					} else {
						sendIntMsg(privateQueue, CS_EXIT_CONFIRMED, CS_LAST_MSG);
					}

					endwin();
				}

			}
		}
		exit(0);
	} else {
		// if ctrl+c pressed
		sendIntMsg(publicServerQueue, CS_EXIT, getpid());
	}
	return 0;
}

void stop() {
	running = 0;
}

void commanderStop(){
	exit(0);
}

// clean all client-own shms, queues, semaphores
void cleanEverything(int clientQueue) {
	msgctl(clientQueue, IPC_RMID, NULL);
	semctl(shmSem, 0, IPC_RMID, NULL);
	shmctl(shmid, IPC_RMID, NULL);
}

WINDOW* create_newwin(int height, int width, int starty, int startx) {
	WINDOW *local_win;

	local_win = newwin(height, width, starty, startx);
	//wborder(local_win, '|', '|', '-', '-', '+', '+', '+', '+');
	wborder(local_win, ACS_VLINE | A_BOLD, ACS_VLINE | A_BOLD, ACS_HLINE | A_BOLD, ACS_HLINE | A_BOLD,
			ACS_ULCORNER | A_BOLD, ACS_URCORNER | A_BOLD, ACS_LLCORNER | A_BOLD, ACS_LRCORNER | A_BOLD);
	box(local_win, 0, 0);
	keypad(local_win, 0);
	wrefresh(local_win);

	return local_win;
}

void updateScreen(int clientQueue, int shmid) {
	int message, counter = 0;
	struct shm *shared = (struct shm*) shmat(shmid, NULL, 0);

	WINDOW* commandWindow;
	WINDOW* logWindow;
	WINDOW* statsWindow;

	commandWindow = create_newwin(COMMAND_LINE_HEIGHT, COLS, LINES - COMMAND_LINE_HEIGHT, 0);
	logWindow = create_newwin(LINES - COMMAND_LINE_HEIGHT, COLS - STATS_WIDTH, 0, 0);
	statsWindow = create_newwin(LINES - COMMAND_LINE_HEIGHT, STATS_WIDTH, 0, COLS - STATS_WIDTH);

	while (running == 1) {
		updateLogWindow(logWindow, counter++);
		updateStatsWindow(statsWindow);
		updateCommandWindow(commandWindow, shared->command);

		message = getIntMsg(clientQueue, 0);

		if (message == C_EXIT_COMMAND) {
			break;
		} else if (message == C_EXIT_FROM_SERVER) {
			killedByServer = 1;
			break;
		}

	}

	destroy_win(commandWindow);
	destroy_win(statsWindow);
	destroy_win(logWindow);
}

void destroy_win(WINDOW *local_win) {
	wborder(local_win, ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ');
	wrefresh(local_win);
	delwin(local_win);
}

void updateLogWindow(WINDOW *win, int c) {
	struct shm *log = (struct shm*) shmat(shmid, NULL, 0);
	wclear(win);
	box(win, 0, 0);
	mvwprintw(win, 0, 0, "%s %d", msgs[LOG_WINDOW_TITLE], log->logsCount);
	// get logs
	int start, end, i, j;

	semDown(shmSem);
	if (log->logsCount - MAX_LOGS < 0) {
		start = 0;
		end = log->logsCount;
	} else {
		start = log->logsCount % MAX_LOGS;
		end = start + MAX_LOGS;
	}

	for (i = start, j = 0; i < end; i++, j++) {
		mvwprintw(win, j + 2, 2, "%s", log->logs[i % MAX_LOGS]);
	}
	semUp(shmSem);

	wrefresh(win);
}

void updateStatsWindow(WINDOW *win) {
	struct shm *stats = (struct shm*) shmat(shmid, NULL, 0);
	wclear(win);
	box(win, 0, 0);
	mvwprintw(win, 0, 0, msgs[STATS_WINDOW_TITLE]);

	int j;
	semDown(shmSem);
	for (j = 0; j <= LAST_ENTITY; j++) {
		mvwprintw(win, j + 2, 2, "%s : %d", entities_strings[j], stats->units[j]);
	}
	mvwprintw(win, j + 4, 2, "%s : %d", entities_strings[j], stats->wins);
	semUp(shmSem);

	wrefresh(win);
}

void updateCommandWindow(WINDOW *win, char* cmd) {
	wclear(win);
	box(win, 0, 0);
	mvwprintw(win, 0, 0, msgs[COMMAND_WINDOW_TITLE]);
	mvwprintw(win, 1, 5, "> %s", cmd);
	wrefresh(win);
}

void logger(int getQueue, int clientQueue, int shmid) {
	struct shm *mem = (struct shm*) shmat(shmid, NULL, 0);
	int log, msg;
	char logText[MAX_LOG_LENGTH];

	// initialize log counter
	semDown(shmSem);
	mem->logsCount = 0;
	semUp(shmSem);

	while (running == 1) {
		msg = getLogMsg(getQueue, CS_LOG);

		if (msg != -1) {
			log = logMsg.logID;

			// get params
			if (log == LOG_WAS_DEFENSE || log == LOG_WAS_ATTACK) {
				sprintf(logText, msgs[log], logMsg.params[LIGHT_INFANTRY], logMsg.params[HEAVY_INFANTRY],
						logMsg.params[CAVALRY]);
			} else {
				strcpy(logText, msgs[log]);
			}

			newLog(logText);

			sendIntMsg(clientQueue, C_NEW_LOG, C_NEW_LOG);
			if (log == LOG_EXIT) {
				sendIntMsg(clientQueue, C_EXIT_FROM_SERVER, C_EXIT_FROM_SERVER);
			} else if (log == LOG_WIN_EXIT) {
				sleep(3);
				sendIntMsg(clientQueue, C_EXIT_COMMAND, C_EXIT_COMMAND);
			}
		}
	}
}

void stats(int getQueue, int clientQueue, int shmid) {
	struct shm *stats = (struct shm*) shmat(shmid, NULL, 0);
	int i, msg;
	while (running == 1) {
		msg = getEntitiesMsg(getQueue, CS_STATS);

		// update shm - copy values from entity message
		if (msg != -1) {
			for (i = 0; i < LAST_ENTITY + 1; i++) {
				stats->units[i] = entitiesMsg.units[i];
			}
			stats->wins = entitiesMsg.wins;

			sendIntMsg(clientQueue, C_NEW_STATS, C_NEW_STATS);
		}
	}
}

void command(int privateQueue, int clientQueue, int shmid) {
	struct shm *cmd = (struct shm*) shmat(shmid, NULL, 0);
	char str[MAX_COMMAND_LENGTH] = "", previous[MAX_COMMAND_LENGTH] = "";
	int length, key;

	commandMsg.type = CS_COMMAND;

	signal(SIGINT, commanderStop);

	while (strcmp(previous, quitCommand) != 0 && running == 1) {
		length = strlen(str);

		// get character
		key = getchar();

		switch (key) {
			// backspace
			case 127 :
				str[length - 1] = '\0';
				break;
				// enter
			case 13 :
				if (strlen(str) > 0) {
					newCommand(privateQueue, str);
					strcpy(previous, str);
					strcpy(str, "");
				}
				break;
			default :
				// if normal key - add to str
				if (key >= 32 && key <= 122) {
					str[length] = (char) key;
					str[length + 1] = '\0';
				}
				break;
		}
		// copy to shared memory
		semDown(shmSem);
		strcpy(cmd->command, str);
		semUp(shmSem);

		// send msg to client queue
		sendIntMsg(clientQueue, C_NEW_COMMAND, C_NEW_COMMAND);

		// if help command
		if(strcmp(previous, helpCommand) == 0){
			newLog(msgs[LOG_HELP]);
		}

	}
	// if quit command
	if (strcmp(previous, quitCommand) == 0) {
		sendIntMsg(clientQueue, C_EXIT_COMMAND, C_EXIT_COMMAND);
	}
}

void newCommand(int privateQueue, char* cmd) {
	char cmdCopy[MAX_COMMAND_LENGTH], *splittedCmd[MAX_COMMAND_LENGTH], *options, tmp[MAX_LOG_LENGTH], *out;
	int cmdCounter = 0, option_char, i, wrongCommand = 1;

	// copy command
	strcpy(cmdCopy, cmd);

	// split command by " " delimiter
	out = strtok(cmd, " ");

	splittedCmd[cmdCounter] = out;

	while (out != NULL) {
		cmdCounter += 1;
		out = strtok(NULL, " ");
		if (out != NULL) {
			splittedCmd[cmdCounter] = out;
		}
	}

	// initialize commandMsg struct
	for (i = FIRST_ENTITY; i <= LAST_ENTITY; i++) {
		commandMsg.params[i] = 0;
	}

	// read first argument - command name
	if (strcmp(splittedCmd[0], createCommand) == 0 || strcmp(splittedCmd[0], attackCommand) == 0) {
		if (strcmp(splittedCmd[0], createCommand) == 0) {
			options = createOptions;
			commandMsg.commandId = COMMAND_CREATE_ENTITIES;
		} else {
			options = attackOptions;
			commandMsg.commandId = COMMAND_ATTACK;
		}

		// reset getopt
		optind = 1;

		// parse each argument and check for errors
		while ((option_char = getopt(cmdCounter, splittedCmd, options)) != -1) {
			switch (option_char) {
				case 'w' :
					commandMsg.params[WORKERS] = atoi(optarg);
					if (commandMsg.params[WORKERS] > 0) {
						wrongCommand = 0;
					} else {
						wrongCommand = 1;
					}
					break;
				case 'l' :
					commandMsg.params[LIGHT_INFANTRY] = atoi(optarg);
					if (commandMsg.params[LIGHT_INFANTRY] > 0) {
						wrongCommand = 0;
					} else {
						wrongCommand = 1;
					}
					break;
				case 'h' :
					commandMsg.params[HEAVY_INFANTRY] = atoi(optarg);
					if (commandMsg.params[HEAVY_INFANTRY] > 0) {
						wrongCommand = 0;
					} else {
						wrongCommand = 1;
					}
					break;
				case 'c' :
					commandMsg.params[CAVALRY] = atoi(optarg);
					if (commandMsg.params[CAVALRY] > 0) {
						wrongCommand = 0;
					} else {
						wrongCommand = 1;
					}
					break;
			}
		}
	} else if (strcmp(cmdCopy, quitCommand) == 0 || strcmp(cmdCopy, helpCommand) == 0) {
		wrongCommand = 2;
	} else {
		wrongCommand = 1;
	}

	// notify server if command was correct
	if (wrongCommand == 0) {
		sendCommandMsg(privateQueue, CS_COMMAND);

	} else if (wrongCommand == 1) {
		sprintf(tmp, "%s - wrong command.", cmdCopy);
		newLog(tmp);
	}
}

void newLog(char* newLog) {
	struct shm *log = (struct shm*) shmat(shmid, NULL, 0);
	char tmp[MAX_LOG_LENGTH], timeBuffer[20];
	time_t rawtime;
	struct tm * timeinfo;

	time(&rawtime);
	timeinfo = localtime(&rawtime);

	strftime(timeBuffer, 20, "%H:%M:%S", timeinfo);
	sprintf(tmp, "%s : %s", timeBuffer, newLog);

	semDown(shmSem);
	strcpy(log->logs[log->logsCount % MAX_LOGS], tmp);
	(log->logsCount)++;
	semUp(shmSem);
}
